﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Reflection;
using Microsoft.VisualStudio.XmlEditor;
using Microsoft.VisualStudio.SharePoint;
using System.IO;

namespace CodeCounsel.SharePoint.RibbonDesigner.Model
{
    public static class DomainModelFactory
    {
        static Dictionary<XName, DomainModelBuilder> _buildersByName;
        static Dictionary<XName, Type> _typesByName;

        static DomainModelFactory()
        {
            _buildersByName = new Dictionary<XName, DomainModelBuilder>();
            _typesByName = new Dictionary<XName, Type>();
            foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
            {
                if (Attribute.GetCustomAttribute(type, typeof(XNameAttribute)) != null)
                {
                    Register(type);
                }
            }
        }

        public static void Register(Type type)
        {
            XNameAttribute nameAttribute = (XNameAttribute)Attribute.GetCustomAttribute(type, typeof(XNameAttribute));
            DomainModelBuilderAttribute builderAttribute = (DomainModelBuilderAttribute)Attribute.GetCustomAttribute(type, typeof(DomainModelBuilderAttribute));
            XName name = nameAttribute.ToName();
            if (builderAttribute != null)
            {
                _buildersByName.Add(name, (DomainModelBuilder)Activator.CreateInstance(builderAttribute.BuilderType));
            }
            _typesByName.Add(name, type);
        }

        public static CommandUIDocument CreateDefinition(CommandUIModel owner, XDocument document)
        {
            return new CommandUIDocument(owner, (CommandUI)CreateModel(
                document.Root, DomainModelSource.Global));
        }

        public static ElementsDocument CreateElementsDefinition(CommandUIModel owner, XDocument document)
        {
            return new ElementsDocument(owner,
                (Elements)CreateModel(document.Root, DomainModelSource.Project));
        }

        public static Elements CreateExtensions(
            CommandUIDocument uiDefinition,
            XmlModel dataModel, 
            DomainModelSource source)
        {
            XDocument dataDocument = dataModel.Document;
            XNamespace ns = Namespaces.CommandUI;
            XElement root = dataDocument.Root;
            Elements model = CreateModel(root, source) as Elements;
            if (model == null)
            {
                throw new InvalidDataException();
            }
            return model;
        }

        public static DomainElement CreateModel(XElement element, DomainModelSource source)
        {
            DomainElement domainElement = null;
            if (element != null)
            {
                Schema.Validate(element);
                DomainModelBuilder builder = _buildersByName.ContainsKey(element.Name) ?
                    _buildersByName[element.Name] :
                    new DomainModelBuilder(_typesByName[element.Name]);
                domainElement = builder.Build(element, source);
            }
            return domainElement;
        }

        public static IEnumerable<T> CreateModelCollection<T>(XElement parent, DomainModelSource source)
            where T : DomainElement
        {
            XNameAttribute attribute = (XNameAttribute)Attribute.GetCustomAttribute(typeof(T), typeof(XNameAttribute));
            XName childElementName = attribute.ToName();
            return parent.Elements(childElementName)
                .Select(child => (T)CreateModel(child, source));
        }

        public static IEnumerable<DomainElement> CreateModelCollection(XElement parent, DomainModelSource source)
        {
            return parent.Elements()
                .Where(e => IsSupportedElement(e))
                .Select(child => CreateModel(child, source));
        }

        public static bool IsSupportedElement(XElement child)
        {
            return _typesByName.ContainsKey(child.Name);
        }
    }
}